package pl.edu.mimuw.mas.editor.widget;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.mimuw.mas.chart.Area;
import pl.edu.mimuw.mas.chart.Chart;
import pl.edu.mimuw.mas.exception.WrongAreaException;

/**
 * Implementacja płótna do podglądu całej mapy i edytowanego obszaru.
 */
public class SelectionCanvas extends Canvas {
	
	private final Logger log = LoggerFactory.getLogger(getClass());
	
	// kolory - kompatybilne ze schematem TANGO
	private static final int[] BG_COLOUR = { 0x2e, 0x34, 0x36 };

	private static final int[] ACTIVE_GRID_COLOUR = { 0x8a, 0xe2, 0x34 }; // zielony
	private static final int[] INACTIVE_GRID_COLOUR = { 0x88, 0x8a, 0x85 }; // szary jasny

	private static final int R = 0, G = 1, B = 2;

	private static final int MARGIN_TOP = 30;
	private static final int MARGIN_BOTTOM = 30;
	private static final int MARGIN_LEFT = 30;
	private static final int MARGIN_RIGHT = 30;
	
	private Area area;
	private Chart chart;
	
	private int mouseX0, mouseY0, mouseX1, mouseY1;
	boolean moving = false;
	
	private int realMarginLeft, realMarginTop;
	private int chartWidth, chartHeight;
	private int rows, columns;
	private double size;
	
	public SelectionCanvas(Composite c, int arg) {
		super(c, arg);
		
		this.addPaintListener(new PaintListener() {
			@Override
			public void paintControl(PaintEvent e) {
				drawSelection(e);
			}
		});
		
		this.addMouseListener(new MouseListener() {
			@Override
			public void mouseDoubleClick(MouseEvent e) {	}
			@Override
			public void mouseDown(MouseEvent e) {
				moving = true;
				mouseX0 = e.x;
				mouseY0 = e.y;
			}
			@Override
			public void mouseUp(MouseEvent e) {
				moving = false;
				mouseX1 = e.x;
				mouseY1 = e.y;
				updateSelection(true);
				log.info("Zaznaczony obszar (" 
						+ "(" + area.getArea()[0] + ", " + area.getArea()[1] + "), " 
						+ "(" + area.getArea()[2] + ", " + area.getArea()[3] + "))");
			}
		});
		
		this.addMouseMoveListener(new MouseMoveListener() {
			@Override
			public void mouseMove(MouseEvent e) {
				if (moving) {
					mouseX1 = e.x;
					mouseY1 = e.y;
					updateSelection(false);
				}
			}
		});
	}
	
	public void setSelection(Area area) {
		this.area = area;
	}
	
	public Area getSelection() {
		return area;
	}
	
	public void setChart(Chart chart) {
		this.chart = chart;
		try {
			area = new Area(0, 0, chart.getColumnsCount() - 1, chart.getRowsCount() - 1);
			Event e = new Event();
			e.data = area; 
			notifyListeners(SWT.Modify, e);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	protected void updateDrawingData() {
		Rectangle clientArea = this.getBounds();
		// Now web representing chart
		rows = chart.getRowsCount();
		columns = chart.getColumnsCount();
		double columnMaxSize = ((double) clientArea.width - MARGIN_LEFT - MARGIN_RIGHT - 1) / (columns - 1);
		double rowMaxSize = ((double) clientArea.height - MARGIN_BOTTOM - MARGIN_TOP - 1) / (rows - 1);
		size = Math.min(rowMaxSize, columnMaxSize);
		chartWidth = (int) (size * (columns - 1));
		chartHeight = (int) (size * (rows - 1));
		realMarginTop = MARGIN_TOP + (clientArea.height - MARGIN_TOP
				- MARGIN_BOTTOM - chartHeight) / 2;
		realMarginLeft = MARGIN_LEFT + (clientArea.width - MARGIN_LEFT
				- MARGIN_RIGHT 
				- chartWidth) / 2;
	}
	
	private void drawSelection(PaintEvent e) {
		updateDrawingData();
		Rectangle clientArea = this.getBounds();
		// First whole canvas to black
		Color bg = new Color(Display.getCurrent(),
                             new RGB(BG_COLOUR[R], BG_COLOUR[G], BG_COLOUR[B]));
		e.gc.setBackground(bg);
		e.gc.setForeground(bg);
		e.gc.fillRectangle(0, 0, clientArea.width, clientArea.height);
		e.gc.setForeground(new Color(Display.getCurrent(),
		                             new RGB(INACTIVE_GRID_COLOUR[R],
		                		             INACTIVE_GRID_COLOUR[G],
		                		             INACTIVE_GRID_COLOUR[B])));
		// draw column lines
		for (int i = 0; i < columns; i++) {
				e.gc.drawLine(realMarginLeft + (int) (i * size),
						realMarginTop,
						realMarginLeft + (int) (i * size),
						realMarginTop + chartHeight);
		}
		// draw row lines
		for (int i = 0; i < rows; i++) {
			e.gc.drawLine(realMarginLeft,
					realMarginTop + (int) (i * size),
					realMarginLeft + chartWidth,
					realMarginTop + (int) (i * size));
		}
		// draw skew lines
		for (int i = 0; i < rows + columns - 1; i++) {
			e.gc.drawLine(
					realMarginLeft + (int) (Math.max(0, i - rows + 1) * size),
					realMarginTop - (int) (Math.min(0, i - rows + 1) * size),
					realMarginLeft + chartWidth + (int) (Math.min(i - columns + 1, 0) * size),
					realMarginTop + chartHeight - (int) (Math.max(i - columns + 1, 0) * size));
		}
		// Now selected area
		int border = Math.min((int) (size / 2), MARGIN_TOP - 5);
		border = Math.max(border, 0);
		Color active = new Color(Display.getCurrent(),
                                 new RGB(ACTIVE_GRID_COLOUR[R],
                                         ACTIVE_GRID_COLOUR[G],
                                         ACTIVE_GRID_COLOUR[B]));
		e.gc.setForeground(active);
		e.gc.setBackground(active);
		e.gc.setAlpha(125);
		e.gc.fillRectangle(
				realMarginLeft + (int) ((area.getArea()[0]) * size) - border,
				realMarginTop + (int) ((rows - 1 - area.getArea()[3]) * size) - border,
				(int) (Math.abs((area.getArea()[2] - area.getArea()[0])) * size + 1) + 2 * border,
				(int) (Math.abs((area.getArea()[3] - area.getArea()[1])) * size + 1) + 2 * border);
	}
	
	private void updateSelection(boolean propagate) {
		int x0 = (int) Math.ceil((Math.min(mouseX0, mouseX1) - realMarginLeft) / size);
		x0 = Math.min(Math.max(0, x0), columns - 1);
		
		int y0 = rows - 1 - (int) Math.floor((Math.max(mouseY0, mouseY1) - realMarginTop) / size);
		y0 = Math.min(Math.max(0, y0), rows - 1);
		
		int x1 = (int) Math.floor((Math.max(mouseX0, mouseX1) - realMarginLeft) / size);
		x1 = Math.min(Math.max(0, x1), columns - 1);
		
		int y1 = rows - 1 - (int) Math.ceil((Math.min(mouseY0, mouseY1) - realMarginTop) / size);
		y1 = Math.min(Math.max(0, y1), rows - 1);
		
		try {
			area = new Area(x0, y0, x1, y1);
			redraw();
			if (propagate) {
				Event e = new Event();
				e.data = area; 
				notifyListeners(SWT.Modify, e);	
			}
		} catch (WrongAreaException e) {
			// Area is not correct so we do nothing
			log.error("Niepoprawny obszar");
		}
	}
	
}
